package org.beangle.notification.mail

import java.io.UnsupportedEncodingException
import java.util.Date
import java.util.LinkedHashMap
import java.util.Map
import java.util.Properties
import javax.mail.AuthenticationFailedException
import javax.mail.MessagingException
import javax.mail.NoSuchProviderException
import javax.mail.Session
import javax.mail.Transport
import javax.mail.internet.InternetAddress
import javax.mail.internet.MimeMessage
import javax.mail.internet.MimeUtility
import org.beangle.commons.lang.Strings
import org.beangle.commons.lang.Throwables
import org.beangle.notification.NotificationException
import org.beangle.notification.NotificationSendException
import JavaMailSender._
import org.beangle.commons.logging.Logging
import org.beangle.notification.Message
import org.beangle.commons.bean.Initializing

object JavaMailSender {

  private val HEADER_MESSAGE_ID = "Message-ID"
}

class JavaMailSender extends MailSender with Logging {
  var javaMailProperties: Properties = new Properties()

  private var session: Session = _

  var protocol: String = "smtp"

  var host: String = _

  var port: Int = -1

  var username: String = _

  var password: String = _

  var from: String = _

  var defaultEncoding: String = _

  protected def createMimeMessage(mailMsg: MailMessage): MimeMessage = {
    val mimeMsg = new MimeMessage(getSession)
    val encoding = Strings.substringAfter(mailMsg.contentType, "charset=")
    mimeMsg.setSentDate(if (null == mailMsg.sentAt) new Date() else mailMsg.sentAt)
    if (null != mailMsg.from) {
      mimeMsg.setFrom(mailMsg.from)
    } else if (null != from) {
      mimeMsg.setFrom(MimeUtils.parseAddress(from, encoding).head)
    }
    addRecipient(mimeMsg, mailMsg)
    try {
      mimeMsg.setSubject(MimeUtility.encodeText(mailMsg.subject, encoding, "B"))
    } catch {
      case e: UnsupportedEncodingException => Throwables.propagate(e)
    }
    val text = mailMsg.text
    val html = Strings.contains(mailMsg.contentType, "html")
    if (html) {
      if (Strings.isEmpty(encoding)) mimeMsg.setContent(text, "text/html") else mimeMsg.setContent(text,
        "text/html;charset=" + encoding)
    } else {
      if (Strings.isEmpty(encoding)) mimeMsg.setText(text) else mimeMsg.setText(text, encoding)
    }
    mimeMsg
  }

  protected def getSession(): Session = {
    synchronized {
      if (this.session == null) this.session = Session.getInstance(this.javaMailProperties)
      this.session
    }
  }

  protected def getTransport(session: Session): Transport = {
    if (this.protocol == null) session.getProperty("mail.transport.protocol")
    session.getTransport(protocol)
  }

  override def send(messages: MailMessage*): collection.Map[Message, Exception] = {
    val result = new collection.mutable.HashMap[Message, Exception]
    var transport: Transport = null
    try {
      transport = getTransport(getSession)
      transport.connect(this.host, this.port, this.username, this.password)
    } catch {
      case ex: AuthenticationFailedException => throw new NotificationException(ex)
      case ex: MessagingException => {
        ex.printStackTrace()
        for (i <- 0 until messages.length) {
          val original = messages(i)
          result.put(original, ex)
        }
        throw new NotificationException("Mail server connection failed", ex)
      }
    }
    try {
      for (m <- messages) {
        try {
          val mimeMessage = createMimeMessage(m)
          if (mimeMessage.getSentDate == null) mimeMessage.setSentDate(new Date())
          val messageId = mimeMessage.getMessageID
          mimeMessage.saveChanges()
          if (messageId != null) {
            mimeMessage.setHeader(HEADER_MESSAGE_ID, messageId)
          }
          transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients)
        } catch {
          case ex: MessagingException => result.put(m, ex)
        }
      }
    } finally {
      try {
        transport.close()
      } catch {
        case ex: MessagingException => ex.printStackTrace()
      }
    }
    result
  }

  private def addRecipient(mimeMsg: MimeMessage, mailMsg: MailMessage): Int = {
    var recipients = 0
    for (to <- mailMsg.to) {
      mimeMsg.addRecipient(javax.mail.Message.RecipientType.TO, to)
      recipients += 1
    }
    for (cc <- mailMsg.cc) {
      mimeMsg.addRecipient(javax.mail.Message.RecipientType.CC, cc)
      recipients += 1
    }
    for (bcc <- mailMsg.bcc) {
      mimeMsg.addRecipient(javax.mail.Message.RecipientType.BCC, bcc)
      recipients += 1
    }
    recipients
  }
}
